package client

import (
	"context"
	"fmt"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/network"
	"github.com/docker/docker/api/types/volume"
	"github.com/docker/docker/daemon/logger/jsonfilelog"
	"github.com/docker/go-connections/nat"
	serviceswarm "gykjgit.dccnet.com.cn/chain/proto/swarm/service"
	"strconv"
	"strings"
	"time"

	"github.com/aberic/gnomon"
	"github.com/aberic/gnomon/log"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/filters"
	"github.com/docker/docker/api/types/mount"
	"github.com/docker/docker/api/types/swarm"
	"github.com/pkg/errors"
	proto "gykjgit.dccnet.com.cn/chain/proto/swarm"
)

// Project Project
type Project struct {
}

func newProject() *Project {
	return &Project{}
}

// Deploy 部署新的项目
func (p *Project) Deploy(ctx context.Context, project *proto.ReqProject) (err error) {
	var (
		containersDeployResult = false
		containersDeploy       = false
	)
	// 判断启动模式正确性
	for _, task := range project.Services {
		containerDeploy := false
		for _, constraint := range task.Deploy.Placement.Constraints {
			if strings.Contains(constraint, "node.custom.run") {
				if containersDeployResult && !containersDeploy {
					return errors.New("deploy node mode error!")
				} else if !containersDeployResult {
					containersDeploy = true
				}
				containerDeploy = true
				break
			}
		}
		if containerDeploy != containersDeploy {
			return errors.New("deploy node mode error!")
		}
		containersDeployResult = true
	}
	if containersDeploy {
		for name, task := range project.Services {
			for _, constraint := range task.Deploy.Placement.Constraints {
				if strings.Contains(constraint, "node.custom.run") {
					constraint1 := strings.Split(gnomon.StringTrim(constraint), "==")[1]
					var cid string
					if cid, err = Obtain().Resource().ContainerID(constraint1); nil != err {
						return
					}
					var cid12 = gnomon.SubString(cid, 0, 12)
					log.Warn("deploy", log.Field("containerID", cid12))
					if _, err = serviceswarm.ProjectDeployContainer(gnomon.StringBuild(cid12, ":20219"), &proto.ReqProjectContainer{
						Name: name,
						Task: task,
					}); nil != err {
						log.Warn("deploy", log.Err(err))
						return
					}
				}
			}
		}
		return nil
	} else {
		return p.serviceDeploy(ctx, project)
	}
}

// Update 部署新的项目
func (p *Project) Update(ctx context.Context, update *proto.ReqProjectUpdate) (err error) {
	var (
		project   = &proto.ReqProject{Name: update.ProjectName, Services: map[string]*proto.Task{update.ServiceName: update.Service}}
		services  []swarm.ServiceSpec
		networkID string
	)
	// 创建project网络
	if networkID, err = p.networkExec(ctx, project.Name); nil != err {
		return errors.Wrap(err, "project deploy error")
	}
	if _, services, err = p.serviceFetch(networkID, project); nil != err {
		return errors.Wrap(err, "project update error")
	}
	log.Info("Update Service ", log.Field("ServiceName", update.ServiceName))
	// 创建project中的服务
	_, err = Obtain().Service().Update(ctx, update.ServiceName, services[0])
	return err
}

// Remove 部署新的项目
func (p *Project) Remove(ctx context.Context, remove *proto.ReqProjectRemove) (err error) {
	return Obtain().Service().Remove(ctx, remove.Name)
}

// ServiceCompare 部署新的项目
func (p *Project) ServiceCompare(ctx context.Context, compare *proto.ServiceCompare) (*proto.ServiceCompare, error) {
	log.Debug("ServiceCompare", log.Field("infos", compare.Infos))
	for _, sc := range compare.Infos {
		tasks, err := Obtain().Service().Process(ctx, filters.NewArgs(filters.Arg("name", sc.Name)))
		if nil != err {
			return nil, err
		}
		log.Debug("ServiceCompare", log.Field("tasks", tasks))
		if len(tasks) == 0 {
			continue
		}
		var (
			timeCompare time.Time
			index       uint64 = 0
			position           = 0
		)
		for p, task := range tasks {
			if p == 0 || task.Status.Timestamp.After(timeCompare) {
				timeCompare = task.Status.Timestamp
				position = p
			}
		}
		log.Debug("ServiceCompare", log.Field("index", index), log.Field("position", position), log.Field("DesiredState", tasks[position].DesiredState))
		sc.State = string(tasks[position].Status.State)
		sc.Image = tasks[position].Spec.ContainerSpec.Image
	}
	return compare, nil
}

// ServiceStats 查询Service所有容器的Stats
func (p *Project) ServiceStats(ctx context.Context, serviceName string, isSwarm bool) (map[string]*proto.ServiceStats, error) {
	var (
		err       error
		svcAllArr []swarm.Service
		svcArr    []swarm.Service

		containers   []types.Container
		containerArr []types.Container

		svcStatsRes = map[string]*proto.ServiceStats{}
	)

	if isSwarm {
		if svcAllArr, err = Obtain().Service().List(ctx); err != nil {
			return nil, err
		}
		for _, svc := range svcAllArr {
			if strings.Contains(svc.Spec.Name, serviceName) {
				svcArr = append(svcArr, svc)
			}
		}
		for _, svc := range svcArr {
			stats, _, _ := Obtain().Container().Stats(ctx, svc.ID)
			if stats != nil {
				if serviceStats := p.parseContainerStats(stats); serviceStats != nil {
					svcStatsRes[svc.Spec.Name] = serviceStats
					// svcStatsRes[svc.Names[0]] = serviceStats
				}
			}
		}
	} else {
		if containers, err = Obtain().Container().List(ctx); err != nil {
			return nil, err
		}
		for _, svc := range containers {
			if strings.Contains(svc.Names[0], serviceName) {
				containerArr = append(containerArr, svc)
			}
		}
		for _, svc := range containerArr {
			stats, _, _ := Obtain().Container().Stats(ctx, svc.ID)
			if stats != nil {
				if serviceStats := p.parseContainerStats(stats); serviceStats != nil {
					svcStatsRes[svc.Names[0]] = serviceStats
				}
			}
		}
	}

	return svcStatsRes, nil
}

// networkExec 判断当前批次services中所依赖网络是否存在，或依赖即将待本次创建网络
func (p *Project) networkExec(ctx context.Context, networkName string) (id string, err error) {
	var (
		networksAlready []types.NetworkResource
	)
	if networksAlready, err = Obtain().network.List(ctx, types.NetworkListOptions{}); nil != err {
		return "", err
	}
	for _, nl := range networksAlready {
		if nl.Name == networkName { // 当前批次service依赖即将待本次创建网络
			if nl.Driver == "overlay" && nl.Scope == "swarm" && nl.Attachable {
				return nl.ID, nil
			}
			return "", fmt.Errorf("network %s already exist but it's not support", networkName)
		}
	}
	var response types.NetworkCreateResponse
	if response, err = Obtain().network.Create(ctx, networkName, types.NetworkCreate{
		Driver:     "overlay",
		Scope:      "swarm",
		Attachable: true,
	}); nil != err {
		return "", err
	}
	return response.ID, nil
}

// volumeExec 判断当前批次services中所依赖网络是否存在，或依赖即将待本次创建网络
func (p *Project) volumeExec(ctx context.Context, volumes []*proto.Volume) error {
	localVolumeList, err := Obtain().volume.List(ctx)
	if nil != err {
		return errors.Wrap(err, "volumeExec error")
	}
	for _, v := range volumes {
		for _, lv := range localVolumeList.Volumes {
			if v.Name == lv.Name {
				return fmt.Errorf("volume %s already exist", v.Name)
			}
		}
	}
	for _, v := range volumes {
		if _, err = Obtain().volume.Create(ctx, volume.VolumeCreateBody{
			Driver:     v.Driver,
			DriverOpts: v.DriverOpts,
			Labels:     v.Labels,
			Name:       v.Name,
		}); nil != err {
			return errors.Wrap(err, "volumeExec Create error")
		}
	}
	return nil
}

// func (p *Project) networkCreate(ctx context.Context, networkName string, networksAlready []types.NetworkResource) (id string, err error) {
//	for _, nl := range networksAlready {
//		if networkName == nl.Name {
//			if nl.Driver == "overlay" && nl.Scope == "swarm" && nl.Attachable {
//				return nl.ID, nil
//			}
//			return "", fmt.Errorf("network %s already exist and it's not support", networkName)
//		}
//	}
//	var response types.NetworkCreateResponse
//	if response, err = Obtain().network.Create(ctx, networkName, types.NetworkCreate{
//		Driver:     "overlay",
//		Scope:      "swarm",
//		Attachable: true,
//	}); nil != err {
//		return "", err
//	}
//	return response.ID, nil
// }

func (p *Project) serviceFetch(networkID string, project *proto.ReqProject) ([]swarm.ServiceSpec, []swarm.ServiceSpec, error) {
	var (
		caService    []swarm.ServiceSpec
		peerServices []swarm.ServiceSpec
		err          error
	)
	for name, task := range project.Services {
		var (
			portConfig                   []swarm.PortConfig
			restartPolicy                *swarm.RestartPolicy
			updateConfig, rollbackConfig *swarm.UpdateConfig
		)
		if portConfig, err = p.servicePorts(task.Ports); nil != err {
			return nil, nil, err
		}
		if restartPolicy, err = p.serviceRestartPolicy(task.Deploy.RestartPolicy); nil != err {
			return nil, nil, err
		}
		if updateConfig, err = p.serviceUpdateConfig(task.Deploy.UpdateConfig); nil != err {
			return nil, nil, err
		}
		if rollbackConfig, err = p.serviceUpdateConfig(task.Deploy.RollbackConfig); nil != err {
			return nil, nil, err
		}
		service := swarm.ServiceSpec{
			Annotations: swarm.Annotations{
				Name:   name,
				Labels: task.Deploy.Labels,
			},
			EndpointSpec: &swarm.EndpointSpec{
				Mode:  p.serviceModeSpec(task.Deploy.EndpointMode),
				Ports: portConfig,
			},
			TaskTemplate: swarm.TaskSpec{
				ContainerSpec: &swarm.ContainerSpec{
					Image:   gnomon.StringBuildSep(":", task.Image.Name, task.Image.Version),
					Env:     p.serviceEnv(task.Environments),
					Dir:     task.WorkingDir,
					Mounts:  p.serviceMount(task.Volumes),
					TTY:     task.TTY,
					Hosts:   p.serviceHosts(task.ExtraHosts),
					Command: task.Command,
					Args:    task.Args,
					Labels:  task.Deploy.Labels,
				},
				Networks:      []swarm.NetworkAttachmentConfig{{Target: networkID, Aliases: task.Networks}},
				Placement:     p.servicePlacement(task),
				RestartPolicy: restartPolicy,
			},
			Mode:           p.serviceMode(task.Deploy),
			UpdateConfig:   updateConfig,
			RollbackConfig: rollbackConfig,
			Networks:       []swarm.NetworkAttachmentConfig{{Target: networkID, Aliases: task.Networks}},
		}
		if len(project.Services) > 1 && task.IsCA {
			caService = append(caService, service)
		} else {
			peerServices = append(peerServices, service)
		}
	}
	return caService, peerServices, nil
}

// serviceDeploy 部署新的项目
func (p *Project) serviceDeploy(ctx context.Context, project *proto.ReqProject) (err error) {
	var (
		caService, peerServices []swarm.ServiceSpec
		networkID               string
	)
	log.Debug("Deploy", log.Field("project", *project))
	// 创建project网络
	if networkID, err = p.networkExec(ctx, project.Name); nil != err {
		return errors.Wrap(err, "project deploy error")
	}
	if err = p.volumeExec(ctx, project.Volumes); nil != err {
		return errors.Wrap(err, "project deploy error")
	}
	if caService, peerServices, err = p.serviceFetch(networkID, project); nil != err {
		return errors.Wrap(err, "project deploy error")
	}
	if err = p.serviceCreate(ctx, caService); nil != err {
		return errors.Wrap(err, "project deploy ca error")
	}
	// 创建project中的服务
	return p.serviceCreate(ctx, peerServices)
}

func (p *Project) serviceCreate(ctx context.Context, services []swarm.ServiceSpec) (err error) {
	var (
		serviceLocals []swarm.Service
		serviceIDs    []string
		rollback      = false
	)
	if serviceLocals, err = Obtain().service.List(ctx); nil != err {
		return err
	}
	for _, s := range services {
		update := false // 首先判定是否需要更新已有service
		for _, serviceLocal := range serviceLocals {
			if serviceLocal.Spec.Name == s.Name {
				if _, err = Obtain().service.Update(ctx, serviceLocal.ID, s); nil != err {
					rollback = true
					break
				}
				update = true
			}
		}
		if rollback {
			break
		}
		if update {
			continue
		}
		var service types.ServiceCreateResponse
		if service, err = Obtain().service.Create(ctx, s); nil != err {
			rollback = true
			break
		}
		serviceIDs = append(serviceIDs, service.ID)
	}
	if rollback {
		for _, serviceID := range serviceIDs {
			errNew := Obtain().service.Remove(ctx, serviceID)
			if nil != errNew {
				err = errors.Wrap(err, errNew.Error())
			}
		}
		return err
	}
	return nil
}

func (p *Project) serviceEnv(envs []*proto.Environment) []string {
	var envArr []string
	for _, env := range envs {
		// if env.Key == "CORE_PEER_NETWORKID" {
		//	envArr = append(envArr, gnomon.StringBuildSep("=", env.Key, "dev"))
		// } else if env.Key == "CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE" {
		//	envArr = append(envArr, gnomon.StringBuildSep("=", env.Key, "host"))
		// } else {
		//	envArr = append(envArr, gnomon.StringBuildSep("=", env.Key, env.Value))
		// }
		envArr = append(envArr, gnomon.StringBuildSep("=", env.Key, env.Value))
	}
	return envArr
}

func (p *Project) serviceModeSpec(mode proto.EndpointMode) swarm.ResolutionMode {
	switch mode {
	default:
		return swarm.ResolutionModeVIP
	case proto.EndpointMode_dnsrr:
		return swarm.ResolutionModeDNSRR
	}
}

func (p *Project) servicePorts(ports []*proto.Port) ([]swarm.PortConfig, error) {
	var (
		pf                      []swarm.PortConfig
		targetPort, publishPort uint64
		err                     error
	)
	for _, port := range ports {
		if targetPort, err = strconv.ParseUint(port.Target, 10, 32); nil != err {
			return nil, err
		}
		if publishPort, err = strconv.ParseUint(port.Publish, 10, 32); nil != err {
			return nil, err
		}
		pf = append(pf, swarm.PortConfig{TargetPort: uint32(targetPort), PublishedPort: uint32(publishPort)})
	}
	return pf, nil
}

func (p *Project) serviceMount(volumes []*proto.ComposeVolume) []mount.Mount {
	var mounts []mount.Mount
	for _, v := range volumes {
		mounts = append(mounts, mount.Mount{Type: p.serviceMountType(v.Type), Source: v.Local, Target: v.Mount})
	}
	return mounts
}

func (p *Project) serviceMountType(mountType string) mount.Type {
	switch mountType {
	default:
		return mount.TypeBind
	case "volume":
		return mount.TypeVolume
	}
}

func (p *Project) serviceNetworkJoin(networks []string, networksAlready []types.NetworkResource) ([]swarm.NetworkAttachmentConfig, error) {
	var networkConfig []swarm.NetworkAttachmentConfig
	for _, n := range networks {
		var haveNetwork = false
		for _, nwa := range networksAlready {
			if n == nwa.Name {
				haveNetwork = true
				networkConfig = append(networkConfig, swarm.NetworkAttachmentConfig{Target: nwa.ID, Aliases: []string{n}})
				break
			}
		}
		if !haveNetwork {
			return nil, fmt.Errorf("network %s not found", n)
		}
	}
	return networkConfig, nil
}

func (p *Project) serviceHosts(extraHosts []*proto.ExtraHost) []string {
	var hosts []string
	for _, extraHost := range extraHosts {
		hosts = append(hosts, gnomon.StringBuildSep(" ", extraHost.HostMap, extraHost.Host))
	}
	return hosts
}

func (p *Project) serviceMode(deploy *proto.Deploy) swarm.ServiceMode {
	switch deploy.Mode {
	default:
		return swarm.ServiceMode{Global: &swarm.GlobalService{}}
	case proto.Mode_replicated:
		return swarm.ServiceMode{Replicated: &swarm.ReplicatedService{Replicas: &deploy.Replicas}}
	}
}

func (p *Project) servicePlacement(task *proto.Task) *swarm.Placement {
	var preferences []swarm.PlacementPreference
	for _, preference := range task.Deploy.Placement.Preferences {
		preferences = append(preferences, swarm.PlacementPreference{Spread: &swarm.SpreadOver{SpreadDescriptor: preference.Spread}})
	}
	return &swarm.Placement{
		Constraints: task.Deploy.Placement.Constraints,
		Preferences: preferences,
	}
}

func (p *Project) serviceRestartPolicy(policy *proto.RestartPolicy) (*swarm.RestartPolicy, error) {
	if nil == policy {
		return nil, nil
	}
	var (
		restartPolicy = &swarm.RestartPolicy{}
		condition     swarm.RestartPolicyCondition
		delay, window time.Duration
		err           error
	)
	restartPolicy.MaxAttempts = &policy.MaxAttempts
	switch policy.Condition {
	default:
		condition = swarm.RestartPolicyConditionAny
	case proto.Condition_none:
		condition = swarm.RestartPolicyConditionNone
	case proto.Condition_onFailure:
		condition = swarm.RestartPolicyConditionOnFailure
	}
	restartPolicy.Condition = condition
	if gnomon.StringIsNotEmpty(policy.Delay) {
		if delay, err = p.parseTimeDuration(policy.Delay); nil != err {
			return nil, err
		}
		restartPolicy.Delay = &delay
	}
	if gnomon.StringIsNotEmpty(policy.Window) {
		if window, err = p.parseTimeDuration(policy.Window); nil != err {
			return nil, err
		}
		restartPolicy.Window = &window
	}
	return restartPolicy, nil
}

func (p *Project) serviceUpdateConfig(policy *proto.UpdateConfig) (*swarm.UpdateConfig, error) {
	if nil == policy {
		return nil, nil
	}
	var (
		updateConfig   = &swarm.UpdateConfig{}
		delay, monitor time.Duration
		err            error
	)
	updateConfig.Parallelism = policy.Parallelism
	updateConfig.FailureAction = p.serviceUpdateConfigAction(policy.FailureAction)
	updateConfig.MaxFailureRatio = policy.MaxFailureRatio
	updateConfig.Order = p.serviceUpdateConfigOrder(policy.Order)
	if gnomon.StringIsNotEmpty(policy.Delay) {
		if delay, err = p.parseTimeDuration(policy.Delay); nil != err {
			return nil, err
		}
		updateConfig.Delay = delay
	}
	if gnomon.StringIsNotEmpty(policy.Monitor) {
		if monitor, err = p.parseTimeDuration(policy.Monitor); nil != err {
			return nil, err
		}
		updateConfig.Monitor = monitor
	}
	return updateConfig, nil
}

func (p *Project) serviceUpdateConfigAction(action proto.Action) string {
	switch action {
	default:
		return "rollback"
	case proto.Action_continue:
		return "continue"
	case proto.Action_pause:
		return "pause"
	}
}

func (p *Project) serviceUpdateConfigOrder(order proto.Order) string {
	switch order {
	default:
		return "stop-first"
	case proto.Order_startFirst:
		return "start-first"
	}
}

func (p *Project) parseTimeDuration(timeStr string) (time.Duration, error) {
	var (
		timeInt64 int64
		err       error
	)
	if strings.HasSuffix(timeStr, "s") {
		if timeInt64, err = strconv.ParseInt(timeStr[:len(timeStr)-1], 10, 64); nil != err {
			return time.Second, err
		}
		return time.Duration(timeInt64) * time.Second, nil
	} else if strings.HasSuffix(timeStr, "m") {
		if timeInt64, err = strconv.ParseInt(timeStr[:len(timeStr)-1], 10, 64); nil != err {
			return time.Second, err
		}
		return time.Duration(timeInt64) * time.Minute, nil
	} else if strings.HasSuffix(timeStr, "h") {
		if timeInt64, err = strconv.ParseInt(timeStr[:len(timeStr)-1], 10, 64); nil != err {
			return time.Second, err
		}
		return time.Duration(timeInt64) * time.Hour, nil
	}
	return time.Second, errors.New("delay suffix only support s/m/h")
}

func (p *Project) parseContainerStats(stats *types.Stats) *proto.ServiceStats {
	var (
		cpuRate  float64
		memRate  float64
		memUsage string
	)

	cpuDelta := stats.CPUStats.CPUUsage.TotalUsage - stats.PreCPUStats.CPUUsage.TotalUsage
	systemDelta := stats.CPUStats.SystemUsage - stats.PreCPUStats.SystemUsage
	cpuRate = float64(cpuDelta) / float64(systemDelta) * float64(len(stats.CPUStats.CPUUsage.PercpuUsage)) * 100.0

	memRate = float64(stats.MemoryStats.Usage) / float64(stats.MemoryStats.Limit) * 100.0
	memUsage = fmt.Sprintf("%s / %s", p.unitConvert(stats.MemoryStats.Usage), p.unitConvert(stats.MemoryStats.Limit))

	return &proto.ServiceStats{
		CpuUsageRage: float32(cpuRate),
		CpuCoreNum:   stats.CPUStats.OnlineCPUs,
		MemUsageRate: float32(memRate),
		MemUsage:     memUsage,
	}
}

func (p *Project) unitConvert(size uint64) string {
	var (
		mb float64
		gb float64
		tb float64
		eb float64
	)

	mb = 1024 * 1024
	gb = mb * 1024
	tb = gb * 1024
	eb = tb * 1024

	if size < 1024 {
		// return strconv.FormatInt(size, 10) + "B"
		return fmt.Sprintf("%.2fB", float64(size)/float64(1))
	} else if size < (1024 * 1024) {
		return fmt.Sprintf("%.2fKB", float64(size)/float64(1024))
	} else if size < (1024 * 1024 * 1024) {
		return fmt.Sprintf("%.2fMB", float64(size)/mb)
	} else if size < (1024 * 1024 * 1024 * 1024) {
		return fmt.Sprintf("%.2fGB", float64(size)/gb)
	} else if size < (1024 * 1024 * 1024 * 1024 * 1024) {
		return fmt.Sprintf("%.2fTB", float64(size)/tb)
	} else { // if size < (1024 * 1024 * 1024 * 1024 * 1024 * 1024)
		return fmt.Sprintf("%.2fEB", float64(size)/eb)
	}
}

// containersDeploy 部署新的项目
func (p *Project) containersDeploy(ctx context.Context, project *proto.ReqProject) (err error) {
	var containerIDs []string
	for name, task := range project.Services {
		var (
			config     *container.Config
			hostConfig *container.HostConfig
			body       container.ContainerCreateCreatedBody
		)

		var (
			portArr []string
			ports   nat.PortSet
			portMap = nat.PortMap{}
		)
		for _, port := range task.Ports {
			portArr = append(portArr, port.Target)
			portMap[nat.Port(port.Target)] = []nat.PortBinding{{HostPort: port.Publish}}
		}
		if ports, _, err = nat.ParsePortSpecs(portArr); nil != err {
			return
		}
		config = &container.Config{
			Hostname:     name,
			ExposedPorts: ports,
			Tty:          task.TTY,
			Env:          p.serviceEnv(task.Environments),
			Cmd:          append(task.Command, task.Args...),
			Image:        gnomon.StringBuildSep(":", task.Image.Name, task.Image.Version),
			Volumes:      p.containerVolumes(task.Volumes),
			WorkingDir:   task.WorkingDir,
			Labels:       task.Deploy.Labels,
		}

		hostConfig = &container.HostConfig{
			Binds: p.containerBinds(task.Volumes),
			LogConfig: container.LogConfig{
				Type: jsonfilelog.Name,
				Config: map[string]string{
					"mode":            string(container.LogModeNonBlock),
					"max-buffer-size": "10m",
				},
			},
			NetworkMode:     "default",
			PortBindings:    portMap,
			RestartPolicy:   container.RestartPolicy{Name: "unless-stopped"},
			ExtraHosts:      p.containerHosts(task.ExtraHosts),
			PublishAllPorts: false,
			Mounts:          p.serviceMount(task.Volumes),
		}
		if body, err = Obtain().Container().Create(ctx, config, hostConfig, &network.NetworkingConfig{}, name); nil != err {
			// 回滚
			for _, containerID := range containerIDs {
				_ = Obtain().Container().Remove(ctx, containerID, types.ContainerRemoveOptions{RemoveVolumes: true, RemoveLinks: true, Force: true})
			}
			return errors.Wrap(err, "deploy mode with container error")
		} else {
			containerIDs = append(containerIDs, body.ID)
		}
	}
	return nil
}

// containersDeploy 部署新的项目
func (p *Project) DeployContainer(ctx context.Context, cont *proto.ReqProjectContainer) (err error) {
	var (
		task       = cont.Task
		config     *container.Config
		hostConfig *container.HostConfig
	)

	var (
		portArr []string
		ports   nat.PortSet
		portMap = nat.PortMap{}
	)
	for _, port := range task.Ports {
		portArr = append(portArr, port.Target)
		portMap[nat.Port(fmt.Sprintf("%s/tcp", port.Target))] = []nat.PortBinding{{HostPort: port.Publish}}
	}
	if ports, _, err = nat.ParsePortSpecs(portArr); nil != err {
		return errors.Wrap(err, "deploy mode with container ports error")
	}
	config = &container.Config{
		Hostname:     cont.Name,
		ExposedPorts: ports,
		Tty:          task.TTY,
		Env:          p.serviceEnv(task.Environments),
		Cmd:          append(task.Command, task.Args...),
		Image:        gnomon.StringBuildSep(":", task.Image.Name, task.Image.Version),
		Volumes:      p.containerVolumes(task.Volumes),
		WorkingDir:   task.WorkingDir,
		Labels:       task.Deploy.Labels,
	}

	hostConfig = &container.HostConfig{
		// Binds: p.containerBinds(task.Volumes),
		LogConfig: container.LogConfig{
			Type: jsonfilelog.Name,
			Config: map[string]string{
				"mode":            string(container.LogModeNonBlock),
				"max-buffer-size": "10m",
			},
		},
		NetworkMode:     "default",
		PortBindings:    portMap,
		RestartPolicy:   container.RestartPolicy{Name: "on-failure"},
		ExtraHosts:      p.containerHosts(task.ExtraHosts),
		PublishAllPorts: false,
		Mounts:          p.serviceMount(task.Volumes),
	}
	var body container.ContainerCreateCreatedBody
	if body, err = Obtain().Container().Create(ctx, config, hostConfig, &network.NetworkingConfig{}, cont.Name); nil != err {
		return errors.Wrap(err, "deploy mode with container create error")
	}
	if err := Obtain().Container().Start(ctx, body.ID, types.ContainerStartOptions{}); err != nil {
		return errors.Wrap(err, "deploy mode with container start error")
	}
	return nil
}

func (p *Project) containerHosts(extraHosts []*proto.ExtraHost) []string {
	var hosts []string
	for _, extraHost := range extraHosts {
		hosts = append(hosts, gnomon.StringBuildSep(":", extraHost.Host, extraHost.HostMap))
	}
	return hosts
}

func (p *Project) containerBinds(volumes []*proto.ComposeVolume) []string {
	var binds []string
	for _, v := range volumes {
		binds = append(binds, strings.Join([]string{v.Local, v.Mount, "rw"}, ":"))
	}
	return binds
}

func (p *Project) containerVolumes(volumes []*proto.ComposeVolume) map[string]struct{} {
	vs := make(map[string]struct{})
	for _, v := range volumes {
		vs[v.Mount] = struct{}{}
	}
	return vs
}
